﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using App1AppHandler;
using System.Timers;
using Reborn_WorldServer;
using System.Drawing;
using System.Data.SqlClient;
using System.IO;
using System.Xml;
using System.Net.Sockets;

namespace Reborn_Server.App1AppHandler
{
    public class HateManager
    {
        //Reference to parent creature
        private CreatureBase myCreatureBase;

        //List of creatures that have attacked and damage they have done
        //This records everything if it is a creature, only other characters if the parent is a character
        private List<CreatureHateInfo> creatures = new List<CreatureHateInfo>();

        private List<CharacterKarmaInfo> karmaFlags = new List<CharacterKarmaInfo>();


        //Object used to lock functions for thread safety
        private object lockObj = new object();

        //Constructor, sets reference to parent creature
        public HateManager(CreatureBase creaturebase)
        {
            myCreatureBase = creaturebase;
        }

        public bool hasKarma()
        {
            lock (karmaFlags)
            {
                return karmaFlags.Count == 0 ? false : true;
            }
        }
        public void AddKarmaFlag(int fromCharactersId, string fromCharactersName, int minutesRemaining)
        {
            lock (karmaFlags)
            {
                CharacterKarmaInfo cki = new CharacterKarmaInfo();
                cki.karmaFromCharacterId = fromCharactersId;
                cki.expires = DateTime.Now.AddMinutes(minutesRemaining);
                cki.karmaFromCharacterName = fromCharactersName;

                karmaFlags.Add(cki);
            }
        }
        public int getKarmaCount()
        {
            lock (karmaFlags)
            {
                return karmaFlags.Count;
            }
        }
        public void clearKarmaFlags()
        {
            lock (karmaFlags)
            {
                karmaFlags.Clear();
            }
        }
        public bool hasKarmaFrom(int characterId)
        {
            lock (karmaFlags)
            {
                foreach (CharacterKarmaInfo cki in karmaFlags)
                {
                    if (cki.karmaFromCharacterId == characterId)
                        return true;
                }
            }
            return false;
        }
        public void removeKarmaEarliestExpire()
        {
            CharacterKarmaInfo karmaToRemove = null;
            lock (karmaFlags)
            {
                foreach (CharacterKarmaInfo cki in karmaFlags)
                {
                    if (karmaToRemove == null || cki.expires < karmaToRemove.expires)
                        karmaToRemove = cki;
                }
                if (karmaToRemove != null)
                    karmaFlags.Remove(karmaToRemove);
            }
        }
        public DateTime getKarmaEarliestExpire()
        {
            DateTime result = DateTime.MaxValue;

            lock (karmaFlags)
            {
                foreach (CharacterKarmaInfo cki in karmaFlags)
                {
                    if (cki.expires < result)
                        result = cki.expires;
                }
            }
            return result;
        }

        public void getKarmaData()
        {
            List<FriendInfo> friendsToRequestFromPrimary = new List<FriendInfo>();
            
            StringWriter stringWriter = new StringWriter();
            XmlTextWriter xmldoc = new XmlTextWriter(stringWriter);

            xmldoc.WriteStartElement("karma");

            lock (karmaFlags)
            {
                foreach (CharacterKarmaInfo k in karmaFlags)
                {
                    xmldoc.WriteRaw(k.toXML());
                }
            }
            xmldoc.WriteEndElement();

            xmldoc.Flush();
            xmldoc.Close();

            stringWriter.Flush();
            string xmlMessage = stringWriter.ToString();

            try
            {
                Character myChar = (Character)myCreatureBase;

                myChar.ClientSocket.socket.BeginSend(xmlMessage);
            }
            catch { }
        }

        public void ResumeSDFlag(int charactersId, DateTime expires)
        {
            CreatureHateInfo chi = new CreatureHateInfo();
            chi.CharacterID = charactersId;
            chi.Damage = 0;
            chi.expireDate = expires;

            creatures.Add(chi);
        }
        public void UpdateList(int charactersID, long nonCharacterCreaturesGUID, int damage, long masterCreatureId)
        {
            lock(lockObj)
            {
                //If this list is for a creature (not vendor, bankers, or other NPCs)
                if (myCreatureBase is Creature && !(myCreatureBase is Vendor) && !(myCreatureBase is Banker) && !(myCreatureBase is Trainer) && myCreatureBase.CreatureType != "Binder" && myCreatureBase.CreatureType != "AlchemyTrainer")
                {
                    Creature myCreature = (Creature)myCreatureBase;

                    bool foundCreature = false;

                    foreach (CreatureHateInfo chi in myCreature.hateManager.creatures)
                    {
                        if (charactersID != -1)
                        {
                            if (charactersID == chi.CharacterID && chi.mastersCreatureId == masterCreatureId)
                            {
                                chi.Damage = chi.Damage + damage;
                                foundCreature = true;
                                break;
                            }
                        }
                        else
                        {
                            if (chi.CreatureID == nonCharacterCreaturesGUID && chi.mastersCreatureId == masterCreatureId)
                            {
                                //Creatures cannot have hate on themselves.
                                if (chi.CreatureID != myCreatureBase.CreatureID)
                                {
                                    chi.Damage = chi.Damage + damage;
                                    foundCreature = true;
                                    break;
                                }
                            }
                        }
                    }

                    if (foundCreature == false)
                    {
                        if (charactersID != -1)
                        {
                            //Pets cannot have hate on their masters
                            Pet_Controller pc = myCreatureBase.petController;
                            if (!(pc != null && pc.MasterCreature is Character && pc.MasterCreature.ID == charactersID) || myCreatureBase is QuestCreature)
                            {
                                CreatureHateInfo newCreatureHateInfo = new CreatureHateInfo();
                                newCreatureHateInfo.CharacterID = charactersID;
                                newCreatureHateInfo.mastersCreatureId = masterCreatureId;
                                newCreatureHateInfo.Damage = damage;
                                myCreature.hateManager.creatures.Add(newCreatureHateInfo);
                            }
                        }
                        //Creatures cannot have hate on themselves or other creatures (we always want players to have more taunt than NPCs), unless the creature is a pet
                        else if (nonCharacterCreaturesGUID != myCreatureBase.CreatureID && masterCreatureId != -1)
                        {
                            CreatureHateInfo newCreatureHateInfo = new CreatureHateInfo();
                            newCreatureHateInfo.CreatureID = nonCharacterCreaturesGUID;
                            newCreatureHateInfo.mastersCreatureId = masterCreatureId;
                            newCreatureHateInfo.Damage = damage;
                            myCreature.hateManager.creatures.Add(newCreatureHateInfo);
                        }
                    }
                    
                }
                //Character attacked by character (cannot have hate on yourself)
                else if (myCreatureBase is Character && charactersID != -1 && charactersID != myCreatureBase.ID)
                {
                    //If we're a pet. Pass the hate to the master.
                    if (myCreatureBase.petController != null)
                    {
                        UpdateList(myCreatureBase.petController.MasterCreature, damage);
                        return;
                    }

                    bool foundCreature = false;
                    foreach (CreatureHateInfo chi in creatures)
                    {
                        if (chi.CharacterID == charactersID)
                        {
                            chi.Damage = chi.Damage + damage;
                            chi.expireDate = DateTime.Now.AddDays(1);

                            foundCreature = true;
                            break;
                        }
                    }
                    if (foundCreature == false)
                    {
                        CreatureHateInfo newCreatureHateInfo = new CreatureHateInfo();
                        newCreatureHateInfo.CharacterID = charactersID;
                        newCreatureHateInfo.Damage = damage;
                        newCreatureHateInfo.expireDate = DateTime.Now.AddDays(1);

                        creatures.Add(newCreatureHateInfo);

                        Character character = (Character)myCreatureBase;
                    }
                }
            }
        }

        public void choosePathAndCreatureToAttack(List<List<MapItem>> inputPaths, List<Point> hiddenList, out List<MapItem> choosenPath, out CreatureBase creatureToAttack)
        {
            choosenPath = null;
            creatureToAttack = null;

            //This function will only get ran by CreatureAI, so we know myCreatureBase is a Creature (not a Character)
            Creature myCreature = (Creature)myCreatureBase;

            
            //These paths will take priority, we can reach these creatures in one move.

            //Set this to -2 or it will never pick up different faction creatures with no hate.
            int maxDamage = -2; //No hate = -1, damage-less hate = 0, hate > 0

            //Records the distance to the choosen group of creatures (Paths may have additional moves that we will throw out)
            int distanceToTravel = 0; //We start looking at our own tile (0 distance)
            
            //Stores all creatures with the highest hate that are within our maximum move distance.
            List<CreatureBase> equalCreatures = new List<CreatureBase>();

            //Stores the path associated with each creature. (Indexes will match)
            List<List<MapItem>> equalPaths = new List<List<MapItem>>();
            
            ///////////////////////////////////////////////////////////////////////////



            //2nd priority paths because they are out of reach for our creature
            
            int maxDamageBeyondMaxMove = -2;
            int currentDistanceBeyondMaxMove = 0;
            List<CreatureBase> equalCreaturesBeyondMaxMove = new List<CreatureBase>();
            List<List<MapItem>> equalPathsBeyondMaxMove = new List<List<MapItem>>();
                        
            ///////////////////////////////////////////////////////////////////


            //Go through each path by shortest distance, we will group if equal hate and same distance, new group if greater hate.
            for (int x = 0; x < 5; x++)
            {
                foreach (List<MapItem> path in inputPaths)
                {
                    //If this path doesnt have enough moves, skip it.
                    if (path.Count < x + 1)
                        continue;

                    MapItem mapItem = path[x];

                    //If this tile is dangerous (has an AOE or something) skip it.
                    if (CreatureAI.hexIsDangerous(myCreature, mapItem))
                        continue;

                    //If this mapitem is hidden, skip it.
                    if (CreatureAI.isHidden(mapItem.MapX, mapItem.MapY, hiddenList))
                        continue;

                    //This position is beyond our creatures max movement, if our max position is dangerous, skip this path.
                    if (x > myCreature.MaxMoves && CreatureAI.hexIsDangerous(myCreature, path[myCreature.MaxMoves]))
                        continue;

                    foreach (CreatureBase creatureBase in mapItem.CreaturesBases)
                    {
                        CreatureHateInfo chi = GetCreatureHateInfo(creatureBase);
                        
                        //Check if creature is valid to attack. (HP > 0,  Different faction or has hate, and is visible (not hidden or invis).
                        if (creatureBase.HP > 0 && (creatureBase.Faction != myCreature.Faction || chi != null) && creatureBase.CanSeeMe(myCreatureBase))
                        {
                            int damageToCompare = -1;
                            if (chi != null)
                                damageToCompare = chi.Damage;

                            //Any options we have that are within our creatures max moves
                            if (x <= myCreature.MaxMoves)
                            {
                                //We already have this creature listed, skip it.
                                if (equalCreatures.Contains(creatureBase))
                                    continue;

                                //Allways add the creature AND its path (even if its a dupe) so the indexes will match, that way we can pick a 
                                //random creature and know the index to its path and its distance.
                                if (damageToCompare == maxDamage && x == distanceToTravel)
                                {
                                    equalPaths.Add(path);
                                    equalCreatures.Add(creatureBase);
                                }
                                //This creature has higher hate so its higher priority, restart the list.
                                else if (damageToCompare > maxDamage)
                                {
                                    equalPaths.Clear();
                                    equalPaths.Add(path);
                                    equalCreatures.Clear();
                                    equalCreatures.Add(creatureBase);
                                    maxDamage = damageToCompare;
                                    distanceToTravel = x;
                                }
                            }
                            //Anything that we cannot reach but we might want to move closer to.
                            else
                            {
                                //We already have this creature listed, skip it.
                                if (equalCreaturesBeyondMaxMove.Contains(creatureBase))
                                    continue;

                                //Allways add the creature AND its path (even if its a dupe) so the indexes will match, that way we can pick a 
                                //random creature and know the index to its path and its distance.
                                if (damageToCompare == maxDamageBeyondMaxMove && x == currentDistanceBeyondMaxMove)
                                {
                                    equalPathsBeyondMaxMove.Add(path);
                                    equalCreaturesBeyondMaxMove.Add(creatureBase);
                                }
                                //This creature has higher hate so its higher priority, restart the list.
                                else if (damageToCompare > maxDamage)
                                {
                                    equalPathsBeyondMaxMove.Clear();
                                    equalPathsBeyondMaxMove.Add(path);
                                    equalCreaturesBeyondMaxMove.Clear();
                                    equalCreaturesBeyondMaxMove.Add(creatureBase);
                                    maxDamageBeyondMaxMove = damageToCompare;
                                    currentDistanceBeyondMaxMove = x;
                                }
                            }
                        }
                    }
                }
            }

            //If we have any options within our creatures range, use them first
            if (equalCreatures.Count > 0)
            {
                //Pick a random creature out of the equal creatures.
                int randomIndex = Managers.GameHandler.random.Next(0, equalCreatures.Count);
                
                //Our number of creatures and paths are equal and in order, so use the same index on both lists.
                creatureToAttack = equalCreatures[randomIndex];

                //Build our chosen path, its going to be the distance to travel
                choosenPath = new List<MapItem>();
                for (int x = 0; x <= distanceToTravel; x++)
                    choosenPath.Add(equalPaths[randomIndex][x]);
            }
            //There are no creatures within range.
            else if (equalCreaturesBeyondMaxMove.Count > 0)
            {
                //Pick a random creature out of the equal creatures.
                int randomIndex = Managers.GameHandler.random.Next(0, equalCreaturesBeyondMaxMove.Count);
                
                //No creature to attack because we cant reach it.
                creatureToAttack = null;

                //Build choosen path, this is a creature that is out of range, so only go up to our max moves. 
                //(We already know that our landing position is not dangerous)
                choosenPath = new List<MapItem>();
                for (int x = 0; x <= myCreature.MaxMoves; x++)
                    choosenPath.Add(equalPathsBeyondMaxMove[randomIndex][x]);
            }
        }

        public List<CreatureBase> prioritizeList(List<CreatureBase> inputList)
        {
            //This will order everything by distance, and randomize same distance.
            List<CreatureBase> result = orderByDistance(inputList);
            
            List<CreatureBase> creaturesWithHate = new List<CreatureBase>();
            List<CreatureBase> creaturesNoHate = new List<CreatureBase>();

            //Create our 2 lists (they will already be in distance or random order, so we ONLY need to prioritize by hate)
            foreach (CreatureBase creatureBase in result)
            {
                if (GetCreatureHateInfo(creatureBase) != null)
                    creaturesWithHate.Add(creatureBase);
                else
                    creaturesNoHate.Add(creatureBase);
            }

            result.Clear();

            lock (this.lockObj)
            {
                while (creaturesWithHate.Count > 0)
                {
                    CreatureBase highestHate = null;
                    int hateValue = -1;
                    foreach (CreatureBase creatureBase in creaturesWithHate)
                    {
                        CreatureHateInfo chi = GetCreatureHateInfo(creatureBase);
                        if (chi.Damage > hateValue)
                        {
                            highestHate = creatureBase;
                            hateValue = chi.Damage;
                        }
                    }
                    result.Add(highestHate);
                    creaturesWithHate.Remove(highestHate);
                }
            }

            while (creaturesNoHate.Count > 0)
            {
                result.Add(creaturesNoHate[0]);
                creaturesNoHate.RemoveAt(0);
            }

            return result;
        }

        private List<CreatureBase> orderByDistance(List<CreatureBase> inputList)
        {
            List<CreatureBase> result = new List<CreatureBase>();

            int currentDistance = 0;
            while (inputList.Count > 0)
            {
                List<CreatureBase> creaturesAtCurrentDistance = new List<CreatureBase>();
                for (int x = 0; x < inputList.Count; x++)
                {
                    CreatureBase creatureBase = inputList[x];

                    int xDistance = Math.Abs(creatureBase.MapX - myCreatureBase.MapX);
                    int yDistance = Math.Abs(creatureBase.MapY - myCreatureBase.MapY);

                    int distance = Math.Max(xDistance, yDistance);

                    if (distance == currentDistance)
                    {
                        creaturesAtCurrentDistance.Add(creatureBase);
                        inputList.Remove(creatureBase);
                        x--;
                    }
                }
                while(creaturesAtCurrentDistance.Count > 0)
                {
                    int randomIndex = Managers.GameHandler.random.Next(0, creaturesAtCurrentDistance.Count);
                    result.Add(creaturesAtCurrentDistance[randomIndex]);
                    creaturesAtCurrentDistance.RemoveAt(randomIndex);
                }
                currentDistance++;
            }

            return result;
        }

        //Updates creature hate info if exists, else creates new info
        public void UpdateList(CreatureBase attackingCreatureBase, int damage)
        {
            //If the attacking creature is a pet, get its master's ID
            long mastersId = -1;
            Pet_Controller pc = attackingCreatureBase.petController;
            if (pc != null)
                mastersId = pc.MasterCreature.CreatureID;

            UpdateList(attackingCreatureBase is Character ? attackingCreatureBase.ID : -1,
                attackingCreatureBase is Creature ? attackingCreatureBase.CreatureID : -1,
                damage, mastersId);
        }

        //Returns a thread safe copy of the creature hate list
        public List<CreatureHateInfo> GetCreatureHateInfoList()
        {
            List<CreatureHateInfo> result = new List<CreatureHateInfo>();

            lock (lockObj)
            {
                //get a thread safe copy of the list
                for (int x = 0; x < creatures.Count; x++)
                {
                    result.Add(creatures[x]);
                }
            }
            return result;
        }

        public CreatureHateInfo GetCharacterHateInfo(int characterId)
        {
            if (characterId < 0)
                return null;

            lock (lockObj)
            {
                foreach (CreatureHateInfo chi in creatures)
                {
                    if (chi.CharacterID == characterId)
                        return chi;
                }
            }
            return null;
        }

        //Returns a creature's hate info, null if no info
        public CreatureHateInfo GetCreatureHateInfo(CreatureBase creaturebase)
        {
            lock (lockObj)
            {
                if (myCreatureBase is Character)
                {
                    for (int x = 0; x < creatures.Count; x++)
                    {
                        if (creatures[x].CharacterID != -1 && creatures[x].expireDate < DateTime.Now)
                        {
                            creatures.RemoveAt(x);
                            x--;
                        }
                    }
                }
                foreach (CreatureHateInfo chi in creatures)
                {
                    long masterGUID = -1;
                    Pet_Controller pc = creaturebase.petController;
                    if (pc != null) masterGUID = pc.MasterCreature.CreatureID;

                    if (creaturebase is Character)
                    {
                        if (chi.CharacterID == creaturebase.ID && chi.mastersCreatureId == masterGUID)
                        {
                            //We dont want to return any hate if it is our master
                            if (myCreatureBase.petController == null || myCreatureBase.petController.MasterCreature.ID != chi.CharacterID)
                                return chi;
                        }
                    }
                    else if (creaturebase is Creature)
                    {
                        if (chi.CreatureID == creaturebase.CreatureID && chi.mastersCreatureId == masterGUID)
                        {
                            //We dont want to return any hate if it is our master
                            if (myCreatureBase.petController == null || myCreatureBase.petController.MasterCreature.CreatureID != chi.CreatureID)
                                return chi;
                        }
                    }
                    
                }
            }
            return null;
        }

        public void ClearAllHateInfo()
        {
            lock (lockObj)
            {
                creatures.Clear();
                //RemoveTimer.Stop();
            }
        }
        public void ClearHateInfo(int CharacterID)
        {
            lock (lockObj)
            {
                foreach (CreatureHateInfo chi in creatures)
                {
                    if (chi.CharacterID == CharacterID)
                    {
                        creatures.Remove(chi);
                        return;
                    }
                }
            }
        }
        public void ClearHateInfo(CreatureBase creaturebase)
        {
            lock (lockObj)
            {
                foreach (CreatureHateInfo chi in creatures)
                {
                    if (creaturebase is Character)
                    {
                        if (chi.CharacterID == creaturebase.ID)
                        {
                            creatures.Remove(chi);
                            return;
                        }
                    }
                    else
                    {
                        if (chi.CreatureID == creaturebase.CreatureID)
                        {
                            creatures.Remove(chi);
                            return;
                        }
                    }
                }
            }
        }
        public void saveSelfDefenseFlags()
        {
            
            SqlConnection MyCon = new SqlConnection(Managers.DBConnectionString);
            MyCon.Open();
            SqlTransaction MyTrans = MyCon.BeginTransaction();

            SqlCommand SQLCom = new SqlCommand();
            SQLCom.CommandType = System.Data.CommandType.StoredProcedure;
            SQLCom.Connection = MyCon;
            SQLCom.Transaction = MyTrans;

            try
            {
                SQLCom.CommandText = "Characters_SDFlags_Delete";
                SQLCom.Parameters.AddWithValue("@CharacterID", myCreatureBase.ID);
                SQLCom.ExecuteNonQuery();

                SQLCom.CommandText = "Characters_SDFlags_Insert";
                //Character ID already exists
                SQLCom.Parameters.Add("@FlaggedCharacterID", System.Data.SqlDbType.Int);
                SQLCom.Parameters.Add("@Expires", System.Data.SqlDbType.DateTime);

                lock (lockObj)
                {
                    foreach (CreatureHateInfo chi in creatures)
                    {
                        if (chi.CharacterID == -1)
                            continue;

                        SQLCom.Parameters["@FlaggedCharacterID"].Value = chi.CharacterID;
                        SQLCom.Parameters["@Expires"].Value = chi.expireDate;

                        SQLCom.ExecuteNonQuery();
                    }
                }

                MyTrans.Commit();
            }
            catch
            {
                MyTrans.Rollback();
            }
            finally
            {
                MyCon.Close();
                MyCon.Dispose();
            }
        }
        
    }

    //Data container class to hold creature info
    public class CreatureHateInfo
    {
        public long CreatureID = -1;
        public int CharacterID = -1;
        public int _Damage = 0;
        public int Damage
        {
            get { return _Damage; }
            set
            {
                if (value < 0)
                    value = 0;
                _Damage = value;
            }
        }

        //This gets set if the creature is pet of a different creature.
        public long mastersCreatureId = -1;

        //This is used for Self defense flags
        public DateTime expireDate;
    }

    public class CharacterKarmaInfo
    {
        public int karmaFromCharacterId;
        public string karmaFromCharacterName;
        public DateTime expires;

        private string calculateExpireTime()
        {
            TimeSpan ts = expires.Subtract(DateTime.Now);

            string result = "";
            if (ts.Days != 0)
                result = result + ts.Days.ToString() + " day(s), ";
            if (ts.Hours != 0)
                result = result + ts.Hours.ToString() + " hour(s), ";
            if (ts.Minutes != 0)
                result = result + ts.Minutes.ToString() + " minute(s)";

            return result.Trim();
        }

        public string toXML()
        {
            string result = "";

            StringWriter stringWriter = new StringWriter();
            XmlTextWriter xmldoc = new XmlTextWriter(stringWriter);

            xmldoc.WriteStartElement("karmainfo");

            xmldoc.WriteAttributeString("name", karmaFromCharacterName);
            xmldoc.WriteAttributeString("expires", calculateExpireTime());

            xmldoc.WriteEndElement();
            xmldoc.Flush();

            xmldoc.Close();

            stringWriter.Flush();

            result = stringWriter.ToString();

            return result;
        }
    }
}
